# -*- encoding: utf-8 -*-

# Copyright 2010 István Szentandrási
# <etude-music-player@googlegroups.com>

# This file is part of Etude.
#
# Etude is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as 
# published by the Free Software Foundation, either version 3 
# of the License, or (at your option) any later version.
#
# Etude is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Etude. If not, see <http://www.gnu.org/licenses/>.

from . import _

import ConfigParser as cp
import copy
import gtk
import time
import urllib2 
import urllib
import webbrowser
import xmlrpclib as xpc

from hashlib import md5
from xml.etree.ElementTree import XML

LASTFM_SECTION = 'lastfm'
API_KEY = 'c85a5ed1ffd26bdb198f5c6f42480224'
SECRET = 'a99fcd36ae52806839f6447a62dd53d2'
RPC_REQ = 'http://ws.audioscrobbler.com/2.0/'


class LastFM(object):

    def __init__(self, config):
        self.config = config

    def init_lastfm(self):
        try:
            self.server = xpc.ServerProxy(RPC_REQ)
        except xpc.ProtocolError:
            return False
        authenticate = False
        try:
            self.lfm_username = self.config.get(LASTFM_SECTION, 'username')
            self.lfm_session = self.config.get(LASTFM_SECTION, 'session')
            self.lfm_subscriber = self.config.get(LASTFM_SECTION, 'subscriber')
        except (cp.NoOptionError, cp.NoSectionError):
            authenticate = True
        if authenticate or self.lfm_session == '':
            return self.authenticate()
        return True

    def authenticate(self):
        session_username, session_key, session_subscriber = ('', '', '')
        try:
            token = self.call_method('auth.getToken', {}, True, True)['0']
            if not self.auth_dialog(token):
                return False
            #token = self.call_method('auth.getToken', {}, True, True)
            session_dict = self.call_method('auth.getSession', {'token': token,
                'method': 'auth.getSession'},
                    True, True)
            session_username = session_dict['0']
            session_key = session_dict['2']
            session_subscriber = session_dict['1']
        except (xpc.Fault, xpc.ProtocolError), ex:
            print(ex)
            return False
        finally:
            self.config.set(LASTFM_SECTION, 'username', session_username)
            self.config.set(LASTFM_SECTION, 'session', session_key)
            self.config.set(LASTFM_SECTION, 'subscriber', session_subscriber)
            self.lfm_username = session_username
            self.lfm_session = session_key
            self.lfm_subscriber = session_subscriber
        return True

    def auth_dialog(self, token):
        dialog = gtk.MessageDialog( None,
                gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                gtk.MESSAGE_QUESTION,
                buttons=gtk.BUTTONS_OK_CANCEL
                )
        dialog.set_title(_("Last.fm authentication"))
        dialog.set_markup(_("To allow Etude to communicate with lastfm using your account, "\
                "you will be now redirected to a last.fm page."))

        resp = dialog.run()
        dialog.destroy()
        if resp == gtk.RESPONSE_OK:
            
            webbrowser.open("http://www.last.fm/api/auth/?api_key="+API_KEY+
                    "&token="+token)

            dialog2 = gtk.MessageDialog( None, 
                    gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                    gtk.MESSAGE_QUESTION,
                    buttons=gtk.BUTTONS_OK
                    )
            dialog2.set_title(_("Last.fm authentication"))
            dialog2.set_markup(_("Please click OK, after you allowed access for Etude."))
            dialog2.run()
            dialog2.destroy()
            return True
        return False

        

    def call_method(self, method, param_dict, api_key = True, api_sig = False,
            sk = False):
        """
        throws Fault, ProtocolError
        """
        if api_key:
            param_dict['api_key'] = API_KEY
        if sk:
            param_dict['sk'] = self.lfm_session
        sig_dict = copy.copy(param_dict)
        #sig_dict['method'] = method
        if api_sig:
            param_dict['api_sig'] = self.construct_api_sig(method, sig_dict)
        #print(method, param_dict)
        res = getattr(self.server, method)( param_dict )
        #print('res', res)
        if isinstance(res, dict):
            return res
        res_tree = XML(res.encode('utf-8').decode('string_quote'))
        #just check one more time, the server should raise Fault if
        #sthing is wrong
        if res_tree.attrib['status'] != 'ok':
            raise xpc.Fault, int(res_tree[0].attrib['code']), res_tree[0].text
        else:
            return res_tree[0]

    def construct_api_sig(self, method, param_dict):
        keys = param_dict.keys()
        keys.sort()
        s = ""
        for key in keys:
            s += key+param_dict[key].encode('utf-8')
        s += SECRET
        return md5(s).hexdigest()


class Scrobbler(object):
    PROTOCOL_VERSION = '1.2.1'
    CLIENT_ID = 'etu'
    CLIENT_VERSION = '0.1'
    MAX_CACHE = 50

    def __init__(self):

        object.__init__(self)
        self.session_id = None
        self.submit_url = None
        self.now_url = None
        self.cache = []

    def init_scrobbler(self, lastfm = None):
        """uninited lastFM type
        first time calling lastfm should be set"""
        if lastfm != None:
            self.last_fm = lastfm
        return self.last_fm.init_lastfm()

    def login(self):
        tstamp = int(time.time())
        token = md5( SECRET + str(tstamp)).hexdigest()
        values = {
                'hs': 'true',
                'p': self.PROTOCOL_VERSION,
                'c': self.CLIENT_ID,
                'v': self.CLIENT_VERSION,
                'u': self.last_fm.lfm_username,
                't': tstamp,
                'a': token,
                'api_key': API_KEY,
                'sk': self.last_fm.lfm_session}
        data = urllib.urlencode(values)
        req = urllib2.Request("http://post.audioscrobbler.com/?"+data)
        try:
            response = urllib2.urlopen(req)
        except urllib2.URLError:
            return (False, _("Problem connecting to audioscrobbler."))
        result = [line.strip() for line in response.readlines()]
        #print(result)
        
        if result[0] == "OK":
            self.session_id = result[1]
            self.now_url = result[2]
            self.submit_url = result[3]
            return (True,)
        elif result[0] == "BANNED":
            return (False, _("Client has been banned, contact author."))
        elif result[0] == "BADAUTH":
            return (False, _("Authentication failed"))
        elif result[0] == "BADTIME":
            return (False, _("Try correcting clock (maybe using ntp client)"))
        elif result[0].startswith("FAILED"):
            return (False, result[0])
        return (False, _("Hard error"))

    def now_playing(self, artist, track, album = '', length = '', tracknr = '', 
            mbid = ''):
        if (self.session_id is None) or (self.now_url is None):
            return (False, _("Error sending now playing.") + 
                    _("Try logging in first."))
        
        values = {
                's' : self.session_id,
                'a' : unicode(artist).encode('utf-8'),
                'b' : unicode(album).encode('utf-8'),
                't' : unicode(track).encode('utf-8'),
                'l' : str(length),
                'n' : str(tracknr),
                'm' : str(mbid) }
        data = urllib.urlencode(values)
        req = urllib2.Request(self.now_url, data)
        try:
            response = urllib2.urlopen(req)
        except urllib2.URLError:
            return (False, _("Problem connecting to audioscrobbler."))
        result = [line.strip() for line in response.readlines()]

        
        if result[0] == "OK":
            #print("LasFM: now playing: ", values)
            return (True, )
        return (False, _("Error sending now playing."))

    def submit(self, artist, track, source='P', rating = '', 
            album = '', length = '', tracknr = '', mbid = ''):
        """
        The client should monitor the user's interaction with the music 
        playing service to whatever extent the service allows. In order 
        to qualify for submission all of the following criteria must be met:

           1. The track must be submitted once it has finished playing.
           Whether it has finished playing naturally or has been manually
           stopped by the user is irrelevant.
           2. The track must have been played for a duration of 
           at least 240 seconds or half the track's total length, whichever 
           comes first. Skipping or pausing the track is irrelevant
           as long as the appropriate amount has been played.
           3. The total playback time for the track must be more 
           than 30 seconds. Do not submit tracks shorter than this.
           4. Unless the client has been specially configured, 
           it should not attempt to interpret filename information to obtain
           metadata instead of using tags (ID3, etc).

        3.2 Submission Stage

        The submission takes place as a HTTP/1.1 POST request to the server,
        using the URL returned by the handshake phase of the protocol.
        The submission request body may contain the details for up to 50 tracks
        which are being submitted. Under normal circumstances only a 
        single track should be submitted per request, however, clients should 
        cache submissions in case of failure.

        The request takes the form of a group of form encoded key-value pairs
        which are submitted to the server as the body of the HTTP POST request, 
        using the URL returned by the handshake request. All specified parameters
        must be present; they should be left empty if not known. The example
        below assumes one track is being submitted as the fields which allow
        multiple values are indexed with zero ([0]). The key-value pairs are:

        s=<sessionID>
            The Session ID string returned by the handshake request. Required.
        a[0]=<artist>
            The artist name. Required.
        t[0]=<track>
            The track title. Required.
        i[0]=<time>
            The time the track started playing, in UNIX timestamp format 
            (integer number of seconds since 00:00:00, January 1st 1970 UTC). 
            This must be in the UTC time zone, and is required.
        o[0]=<source>
            The source of the track. Required, must be one of the following codes:

            P
                Chosen by the user (the most common value, unless you have 
                a reason for choosing otherwise, use this).
            R
                Non-personalised broadcast (e.g. Shoutcast, BBC Radio 1).
            E
                Personalised recommendation except Last.fm (e.g. Pandora, Launchcast).
            L
                Last.fm (any mode). In this case, the 5-digit Last.fm
                recommendation key must be appended to this source ID 
                to prove the validity of the submission (for example, "o[0]=L1b48a").
            U
                Source unknown.

        r[0]=<rating>
            A single character denoting the rating of the track. Empty if not applicable.

            L
                Love (on any mode if the user has manually loved the track). This implies a listen.
            B
                Ban (only if source=L). This implies a skip, and the client 
                should skip to the next track when a ban happens.
            S
                Skip (only if source=L)

            Note: Currently, a web-service must also be called to set love/ban status.
            We anticipate that this will be phased out soon, and the submission 
            service will handle the whole process.
        l[0]=<secs>
            The length of the track in seconds. Required when the source is P, 
            optional otherwise.
        b[0]=<album>
            The album title, or an empty string if not known.
        n[0]=<tracknumber>
            The position of the track on the album, or an empty string if not known.
        m[0]=<mb-trackid>
            The MusicBrainz Track ID, or an empty string if not known. 
        """
        
        values = {
                'i' : str(int(time.time())),
                'o' : unicode(source).encode('utf-8'),
                'a' : unicode(artist).encode('utf-8'),
                'b' : unicode(album).encode('utf-8'),
                't' : unicode(track).encode('utf-8'),
                'r' : rating,
                'l' : str(length),
                'n' : str(tracknr),
                'm' : str(mbid) }
        self.cache.append(values)
         
        if len(self.cache)> self.MAX_CACHE:
            self.cache.pop(0)
        
        return self.__flush()


    def __flush(self):
        if (self.session_id is None) or (self.submit_url is None):
            return (False, _("Error submitting to audioscrobbler.") +
                    _("Try logging in first."))
        
        if len(self.cache) == 0:
            return (True, )

        values = {}
        for i, item in enumerate(self.cache):
            for key in item:
                values[key+"[%d]" % i] = item[key]

        values['s'] = self.session_id
        data = urllib.urlencode(values)
        req = urllib2.Request(self.submit_url, data)
        try:
            response = urllib2.urlopen(req)
        except urllib2.URLError:
            return (False, _("Problem connecting to audioscrobbler."))
        result = [line.strip() for line in response.readlines()]

        if result[0] == "OK":
            self.cache = []
            #print("LasFM: submit: ", values)
            return (True, )
        elif result[0].startswith("FAILED"):
            return (False, result[0])
        return (False, _("Error submitting to audioscrobbler."))

